import ASingleton from "../Abstract/ASingleton";
import { IHttp } from "../Interface/IHttp";

const { ccclass, property, executionOrder } = cc._decorator;

export enum HttpType {
    GET = "GET",
    PUT = "PUT",
    POST = "POST",
    DELETE = "DELETE"
}

interface QueryOption {
    uri?: string,
    url?: string,
    method?: string,
    query?: object;
    headers?: object;
    body?: object;
    timeout?: number;
    json?: boolean;
    withCredentials?: boolean;
}

class Service implements IHttp {
    readonly domain: string;
    readonly headers: object | undefined;
    constructor(uri: string, headers?: object) {
        this.domain = uri;
        this.headers = headers;
    }

    private uploadServerMsgTimeUse(startTime: number, key: string, type: number = 0) {
        const endTime = Date.now();
        const time = endTime - startTime
        const types = ['websocket', 'http'];
        cc.log(`---------${types[type]}  msg :: ${key}  use time:::${time}`);
    }

    private request_promise(opts: QueryOption): Promise<any> {
        const timeStart = Date.now();
        let self = this;
        return new Promise((resolve, reject) => {
            let retry = {
                setting: 5,
                count: 0
            }

            let sendFunc = () => {

                const req = new XMLHttpRequest();
                req.timeout = opts.timeout || 15000;
                if (!cc.sys.isNative) {
                    req.withCredentials = opts.withCredentials || false;
                }

                const timeOut = setTimeout(() => {
                    cc.log('http time out!!!!!!!!!!!!!!');
                    if (retry.count < retry.setting) {
                        retry.count++;
                        sendFunc();
                    }
                    else {
                        self.uploadServerMsgTimeUse(timeStart, opts.uri, 1);
                        resolve({ status: 500, headers: req.getAllResponseHeaders(), body: { code: 500 } });
                    }

                }, req.timeout || 15000);


                cc.log('http+ = ' + JSON.stringify(opts));

                req.onreadystatechange = function () {
                    if (req.readyState === 4) {
                        cc.log('http get responce!!!!!!!!', req.status);
                        cc.log('http get responce!!!!!!!!', req.readyState);
                        clearTimeout(timeOut);
                        if (req.status >= 200 && req.status < 300) {
                            try {
                                self.uploadServerMsgTimeUse(timeStart, opts.uri, 1);
                                resolve({ status: req.status, headers: req.getAllResponseHeaders(), body: JSON.parse(req.responseText) });
                            } catch (_) {
                                self.uploadServerMsgTimeUse(timeStart, opts.uri, 1);
                                resolve({ status: req.status, headers: req.getAllResponseHeaders(), body: req.responseText });
                            }
                        } else if (req.status > 0) {
                            self.uploadServerMsgTimeUse(timeStart, opts.uri, 1);
                            resolve({ status: req.status, headers: req.getAllResponseHeaders() });
                        }
                    }
                };

                req.onerror = reject;

                req.open(opts.method || 'GET', opts.uri || opts.url, true);

                if (opts.headers) {
                    for (let i in opts.headers) {
                        req.setRequestHeader(i, opts.headers[i]);
                    }
                }

                if (opts.body) {
                    if (opts.json) {
                        req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
                    }
                    req.send(typeof opts.body === 'object' ? JSON.stringify(opts.body) : opts.body);
                    return;
                }
                req.send();

            }

            sendFunc();
        });
    };

    public async get(url: string, body?: object, json?: boolean): Promise<{ status: number, body: any, headers: object } | undefined> {
        return await this.request_promise({ method: 'GET', uri: `${this.domain}${url}`, headers: this.headers, body, json }).catch(e => {
            cc.error('http+getError = ' + JSON.stringify(e));
        });
    }

    public async post(url: string, body?: object, json?: boolean): Promise<{ status: number, body: any, headers: object } | undefined> {
        return await this.request_promise({ method: 'POST', uri: `${this.domain}${url}`, headers: this.headers, body, json }).catch(e => {
            cc.error('http+postError = ' + JSON.stringify(e));
        });
    }
}

declare let YaMD5: any;

@ccclass
@executionOrder(-11)
export default class Http extends ASingleton {
    public Send(url: string, cb: (status: number, data: any) => void, param: string | ArrayBuffer | Object | null = null, type: HttpType = HttpType.GET, header: object = { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" }, isText: boolean = true) {
        try {
            let self = this;
            let xhr = new XMLHttpRequest();

            let timeout = () => {
                xhr.abort();
            }

            xhr.onreadystatechange = function () {
                switch (xhr.readyState) {
                    case 0:     // 0 请求未初始化（还没有调用 open()）
                        break;
                    case 1:     // 1 请求已经建立，但是还没有发送（还没有调用 send()）
                        break;
                    case 2:     // 2 请求已发送，正在处理中（通常现在可以从响应中获取内容头）
                        break;
                    case 3:     // 3 请求在处理中；通常响应中已有部分数据可用了，但是服务器还没有完成响应的生成
                        break;
                    case 4:     // 4 响应已完成
                        self.unschedule(timeout);
                        if ((xhr.status == 200 && xhr.status < 400)) {
                            if (isText) {
                                cb(xhr.status, xhr.responseText);
                            }
                            else {
                                cb(xhr.status, xhr.response);
                            }
                        }
                        else {
                            cb(xhr.status, null);
                        }
                        break;
                    default:
                        cb(xhr.status, null);
                        break;
                }
            }

            self.scheduleOnce(timeout, 15);
            this.CommonSend(xhr, type, url, header, param);
        } catch (error) {
            //console.log("[Http].error",error);
            this.Send(url, cb, param, type, header, isText);
        }


    }

    public async AsyncSend(url: string, param: string | ArrayBuffer | Object | null = null, type: HttpType = HttpType.GET, header: object = { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" }, isText: boolean = true) {
        let resolve, reject;
        let ret = new Promise<string>((r, f) => { resolve = r; reject = f; });

        let self = this;
        let xhr = new XMLHttpRequest();

        let timeout = () => {
            xhr.abort();
        }
        xhr.onreadystatechange = function () {
            switch (xhr.readyState) {
                case 0:     //请求未初始化（还没有调用 open()）
                    break;
                case 1:     //请求已经建立，但是还没有发送（还没有调用 send()）
                    break;
                case 2:     //请求已发送，正在处理中（通常现在可以从响应中获取内容头）
                    break;
                case 3:     //请求在处理中；通常响应中已有部分数据可用了，但是服务器还没有完成响应的生成
                    break;
                case 4:     //响应已完成
                    self.unschedule(timeout);
                    if ((xhr.status >= 200 && xhr.status < 400)) {
                        if (isText) {
                            resolve(xhr.responseText);
                        }
                        else {
                            resolve(xhr.response);
                        }
                    }
                    else {
                        cc.error('[Http  AsyncSend fail] : ' + url);
                        reject(xhr.status);
                    }
                    break;
            }
        }

        self.scheduleOnce(timeout, 15);

        this.CommonSend(xhr, type, url, header, param);
        return ret;
    }

    public URLSplicing(url: string, json: Object): string    //拼接URL
    {
        return url + "?" + this.ParamSplicing(json);
    }

    public ParamSplicing(data: object) {
        let param: string = "";
        for (const key in data) {
            param += key + "=" + data[key] + "&";
        }
        return param.substring(0, param.length - 1)
    }

    public Sign(data: object, key: string): string {
        let kArray: Array<string> = new Array<string>();
        for (const k in data) {
            kArray.push(k);
        }
        kArray.sort();

        let param: string = "";
        for (const idx in kArray) {
            let k = kArray[idx];
            param += k + "=" + data[k] + "&";
        }
        param += key;

        let md5 = new YaMD5();
        md5.start();
        md5.appendStr(param);
        return md5.end();
    }

    private CommonSend(xhr: XMLHttpRequest, type: HttpType, url: string, header: object, param: any) {
        xhr.open(type, url, true);
        for (const key in header) {
            xhr.setRequestHeader(key, header[key]);
        }

        if (param == null) {
            xhr.send();
        }
        else {

            if (param instanceof ArrayBuffer) {
                xhr.send(param);
            }
            else if (typeof param === 'object') {
                xhr.send(this.ParamSplicing(param as object));
            }
            else    //String 或 其他
            {
                xhr.send(param);
            }
        }
    }


    public AddService(url: string, headers?: object, name: string = "main") {
        let service = new Service(url, headers);
        this.mService.set(name, service);
    }

    public ClearService() {
        Http.obj.mService.clear();
    }

    public static service(name: string = "main"): IHttp {
        if (Http.obj.mService.has(name)) {
            return Http.obj.mService.get(name);
        }
        else {
            throw new Error("Http service does not exist : " + name);
        }
    }

    protected mService: Map<string, Service> = new Map<string, Service>();

    protected static mInstance: Http = null;
    public static get obj(): Http {
        if (Http.mInstance == null) {
            console.log("[Http.obj] inital game first.");
        }
        return Http.mInstance;
    }
}